﻿using System;
using System.IO;
using System.Xml.Schema;
using Autofac;
using Autofac.Builder;
using Autofac.Configuration;
using Commons.Collections;
using NVelocity.App;
using NVelocity.Exception;
using uTILLIty.Rave.Contract.StorageAdapter;
using uTILLIty.Rave.Core.Manager;
using uTILLIty.Rave.Core.Schema;
using uTILLIty.Rave.Core.StorageAdapter;
using uTILLIty.Rave.Core.Strategy;

namespace uTILLIty.Rave.Core
{
  /// <summary>
  /// The main container module which defines all known services
  /// </summary>
  /// <remarks>
  /// the autofac container is being used as the backbone for the IoC implemenation of the DirCreator
  /// </remarks>
  public class IocModule : Module
  {
    /// <summary>
    /// Adds registrations to the container
    /// </summary>
    /// <param name="builder">The builder through which components can be registered.</param>
    protected override void Load(ContainerBuilder builder)
    {
      base.Load(builder);

      builder.Register(c => GetByKeyWithFallback<IConfigurationApplicatorStrategy>(WellKnownStrategies.Default, c.Resolve<IComponentContext>()));
      builder.Register(c => GetByKeyWithFallback<IConfigurationConstructorStrategy>(WellKnownStrategies.Default, c.Resolve<IComponentContext>()));
      builder.Register(c => GetByKeyWithFallback<IConfigurationValidatorStrategy>(WellKnownStrategies.Default, c.Resolve<IComponentContext>()));
      builder.Register(c => GetByKey<IStorageAdapter>(c.Resolve<IComponentContext>()));
      builder.Register(c => GetVelocityEngineFactory(c.Resolve<IComponentContext>()));
      builder.Register(c => GetApplicatorContextFactory(c.Resolve<IComponentContext>()));
      builder.RegisterType<ConfigurationManager>();
      builder.RegisterType<CsvManager>();
      builder.RegisterType<JobManager>();
      builder.RegisterType<ApplicatorContext>();

      // ReSharper disable AccessToStaticMemberViaDerivedType
      RegisterValidationSchema<WellKnownSchemas>(builder, "NtfsConfiguration.xsd", WellKnownSchemas.RaveNtfsConfiguration);
      RegisterValidationSchema<Contract.Schema.WellKnownSchemas>(builder, "Configuration.xsd", WellKnownSchemas.RaveConfiguration);
      // ReSharper restore AccessToStaticMemberViaDerivedType

      RegisterByKey<NtfsStorageAdapter, IStorageAdapter>(builder, WellKnownStorageAdapters.NtfsFileSystem);
			//don't SingleInstance the DefaultConfigurationApplicatorStrategy, as synchronization in multi-threaded environments will occur otherwise
      RegisterByKey<DefaultConfigurationApplicatorStrategy, IConfigurationApplicatorStrategy>(builder, WellKnownStrategies.Default);
      RegisterByKey<DefaultConfigurationConstructorStrategy, IConfigurationConstructorStrategy>(builder, WellKnownStrategies.Default);
			RegisterByKey<DefaultConfigurationValidatorStrategy, IConfigurationValidatorStrategy>(builder, WellKnownStrategies.Default).SingleInstance();

      TryLoadExternalRegistrations(builder);
    }

    private void TryLoadExternalRegistrations(ContainerBuilder builder)
    {
      try
      {
        var reader = new ConfigurationSettingsReader();
        builder.RegisterModule(reader);
      }
      catch (ArgumentException) //section does not exist, nothing to load
      {
      }
    }

    /// <summary>
    /// Register an additional validation schema with the container
    /// </summary>
    /// <remarks>
    /// Inherit from <see cref="IocModule"/> and override the <see cref="Load"/> method, from within which
    /// you can call this method
    /// </remarks>
    /// <typeparam name="T">The type which's assembly is used to resolve the <paramref name="resourceName"/> to extract the actual schema, when requested</typeparam>
    /// <param name="builder">the builder to register the schema with</param>
    /// <param name="resourceName">The name of the resource to locate within the assembly specified by <typeparamref name="T"/></param>
    /// <param name="namespace">The namespace by which the schema can also be resolved from the container</param>
    public virtual void RegisterValidationSchema<T>(ContainerBuilder builder, string resourceName, string @namespace)
    {
      builder.Register(c => WellKnownSchemas.GetSchemaResource<T>(resourceName))
        .As<XmlSchema>() //for collection
        .Named(@namespace, typeof(XmlSchema));
    }

    /// <summary>
    /// Register an additional implementation of <typeparamref name="TI"/> with the container by key
    /// </summary>
    /// <remarks>
    /// Inherit from <see cref="IocModule"/> and override the <see cref="Load"/> method, from within which
    /// you can call this method
    /// </remarks>
    /// <typeparam name="T">A type implementing <typeparamref name="TI"/> to register</typeparam>
    /// <typeparam name="TI">The type to register <typeparamref name="T"/> by</typeparam>
    /// <param name="builder">the builder to register the <see cref="IStorageAdapter"/> with</param>
    /// <param name="key">the <see cref="uTILLIty.Rave.Contract.Configuration.Configuration.TargetSystem"/> to associate this adapter with</param>
    /// <returns>returns the registration</returns>
    public virtual IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle> RegisterByKey<T, TI>(
      ContainerBuilder builder, string key)
      where T : class, TI
    {
      return builder.RegisterType<T>()
        //.As<TI>() //only required if you like to retrieve an array of registered instances
        .Named(key, typeof(TI));
    }

    /// <summary>
    /// Returns a factory for generating <typeparamref name="T"/>s by it's key (<see cref="Contract.Configuration.Configuration.TargetSystem"/>)
    /// </summary>
    /// <param name="context">The IoC context to resolve new <typeparamref name="T"/>s from</param>
    /// <returns>a factory for generating <typeparamref name="T"/>s by it's key (<see cref="Contract.Configuration.Configuration.TargetSystem"/>)</returns>
    protected virtual Func<string, T> GetByKey<T>(IComponentContext context)
    {
      return key =>
      {
        object storageAdapter;
        if (!context.TryResolveNamed(key, typeof(T), out storageAdapter))
          throw new NotSupportedException(string.Format(Properties.ExceptionMessages.IocServiceNotFound, typeof(T).Name, key));
        return (T)storageAdapter;
      };
    }

    /// <summary>
    /// Returns a factory for generating <typeparamref name="T"/>s by it's key (<see cref="Contract.Configuration.Configuration.TargetSystem"/>)
    /// </summary>
    /// <param name="fallbackKey">The key to try, if the supplied key was not found</param>
    /// <param name="context">The IoC context to resolve new <typeparamref name="T"/>s from</param>
    /// <returns>a factory for generating <typeparamref name="T"/>s by it's key (<see cref="Contract.Configuration.Configuration.TargetSystem"/>)</returns>
    protected virtual Func<string, T> GetByKeyWithFallback<T>(string fallbackKey, IComponentContext context)
    {
      return key =>
      {
        object storageAdapter;
        if (!context.TryResolveNamed(key, typeof(T), out storageAdapter))
          if (!context.TryResolveNamed(fallbackKey, typeof(T), out storageAdapter))
            throw new NotSupportedException(string.Format(Properties.ExceptionMessages.IocServiceNotFound, typeof(T).Name, key));
        return (T)storageAdapter;
      };
    }

    /// <summary>
    /// Returns a factory for generating a new <see cref="ApplicatorContext"/> instance
    /// </summary>
    /// <param name="context">The IoC context to resolve new <see cref="ApplicatorContext"/>s from</param>
    /// <returns>a factory for generating a new <see cref="ApplicatorContext"/> instance</returns>
    protected virtual Func<Contract.Configuration.Configuration, ApplicatorContext> GetApplicatorContextFactory(IComponentContext context)
    {
      return cfg => context.Resolve<ApplicatorContext>(new TypedParameter(typeof(Contract.Configuration.Configuration), cfg));
    }

    /// <summary>
    /// Returns a factory for generating a new <see cref="VelocityEngine"/> instance
    /// </summary>
    /// <param name="context">The IoC context to resolve new <see cref="VelocityEngine"/>s from</param>
    /// <returns>a factory for generating a new <see cref="VelocityEngine"/> instance</returns>
    protected virtual Func<string, VelocityEngine> GetVelocityEngineFactory(IComponentContext context)
    {
      return templatePath =>
      {
        var properties = new ExtendedProperties();
        //properties.SetProperty("resource.loader", "file");
        //properties.SetProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");

        if (!string.IsNullOrWhiteSpace(templatePath))
        {
          var dir = new DirectoryInfo(templatePath); //ensure proper formatting
          properties.SetProperty("file.resource.loader.path", dir.FullName);
        }

        var engine = new VelocityEngine();
        try
        {
          engine.Init(properties);
        }
        catch (ResourceNotFoundException) { } //always occurs, looking for defaults
        return engine;
      };
    }
  }
}